package com.share.order.receiver;

import com.rabbitmq.client.Channel;
import com.share.common.rabbit.constant.MqConst;
import com.share.order.config.DeadLetterMqConfig;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 测试消息接收者
 * 用于接收和处理各类测试消息，包括普通消息、确认消息和延迟消息
 * 主要用于验证消息队列的基本功能和可靠性机制
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TestReceiver {

    /**
     * Redis模板，用于实现消息处理的幂等性控制
     */
    private final RedisTemplate redisTemplate;

    /**
     * 监听普通测试消息
     * 处理来自测试交换机的普通消息，验证基础消息接收的基本功能
     *
     * @param content 消息内容字符串
     * @param message 原始消息对象，包含消息体和属性
     */
    @SneakyThrows // Lombok注解，自动处理异常抛出
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = MqConst.EXCHANGE_TEST, durable = "true"), // 绑定测试交换机，持久化
            value = @Queue(value = MqConst.QUEUE_TEST, durable = "true"),         // 绑定测试队列，持久化
            key = MqConst.ROUTING_TEST)                                           // 绑定测试路由键
    )
    public void test(String content, Message message) {
        // 两种方式打印消息内容：直接通过content参数或从message对象中获取
        log.info("接收普通测试消息：{}", content);
        log.info("接收普通测试消息（从消息体获取）：{}", new String(message.getBody()));
        // 注：此方法未实现手动确认，默认使用自动确认模式
    }

    /**
     * 监听确认模式测试消息
     * 演示消息手动确认机制，确保消息被可靠处理
     *
     * @param content  消息内容字符串
     * @param message  原始消息对象
     * @param channel  消息通道，用于手动确认消息
     */
    @SneakyThrows
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = MqConst.EXCHANGE_TEST, durable = "true"), // 绑定测试交换机
            value = @Queue(value = MqConst.QUEUE_CONFIRM, durable = "true"),       // 绑定确认测试队列
            key = MqConst.ROUTING_CONFIRM)                                         // 绑定确认测试路由键
    )
    public void confirm(String content, Message message, Channel channel) {
        log.info("接收确认模式消息：{}", content);

        // 手动手动确认消息：消息处理完成后通知RabbitMQ可以删除该消息
        // 参数1：消息的唯一标识（DeliveryTag）
        // 参数2：是否批量确认（false表示只确认当前消息）
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    /**
     * 监听死信队列的延迟消息
     * 处理通过死信机制实现的延迟消息，包含幂等性控制
     *
     * @param msg     消息内容字符串
     * @param message 原始消息对象
     * @param channel 消息通道，用于手动确认消息
     */
    @SneakyThrows
    @RabbitListener(queues = {DeadLetterMqConfig.queue_dead_2}) // 监听死信队列
    public void getDeadLetterMsg(String msg, Message message, Channel channel) {
        log.info("接收到死信延迟消息：{}", msg);

        // 基于Redis实现消息处理的幂等性控制，防止重复处理
        // 生成唯一键：前缀+消息内容（实际应用中建议使用业务唯一标识）
        String key = "mq:" + msg;

        // 使用setIfAbsent实现分布式锁：若键存在则设置成功（返回true），若则返回false
        // 过期时间200秒，避免死锁
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "", 200, TimeUnit.SECONDS);

        if (!flag) {
            // 锁已存在，说明该消息已处理过，直接重复处理
            log.info("消息已处理，避免重复执行：{}", msg);
            // 手动确认消息，防止消息重新入队
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }

        // TODO实际业务逻辑处理（此处为示例，需根据实际业务实现）
        // TODO: 执行延迟延迟消息对应的业务操作

        // 业务处理完成后，手动确认消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        log.info("死信延迟消息处理完成：{}", msg);
    }

}